// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.

using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
using System.Reflection;
using AtomUI.Controls.Data;
using AtomUI.Controls.Utils;
using AtomUI.Utils;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Layout;
using Avalonia.Markup.Xaml.MarkupExtensions;
using Avalonia.Utilities;

namespace AtomUI.Controls;

public partial class DataGrid
{
    #region 内部属性定义

    internal DataGridColumnHeadersPresenter? ColumnHeaders => _columnHeadersPresenter;
    internal DataGridColumnCollection ColumnsInternal { get; }
    internal ObservableCollection<IDataGridColumnGroupItem> ColumnGroupsInternal { get; }

    internal List<DataGridColumn> ColumnsItemsInternal => ColumnsInternal.ItemsInternal;
    internal bool InDisplayIndexAdjustments { get; set; }

    internal bool IsColumnHeadersVisible =>
        (HeadersVisibility & DataGridHeadersVisibility.Column) == DataGridHeadersVisibility.Column;

    internal int CurrentColumnIndex
    {
        get => CurrentCellCoordinates.ColumnIndex;
        private set => CurrentCellCoordinates.ColumnIndex = value;
    }

    // the sum of the widths in pixels of the scrolling columns preceding
    // the first displayed scrolling column
    internal double HorizontalOffset
    {
        get => _horizontalOffset;
        set
        {
            if (value < 0)
            {
                value = 0;
            }

            double widthNotVisible = Math.Max(0, ColumnsInternal.VisibleEdgedColumnsWidth - CellsWidth);
            if (value > widthNotVisible)
            {
                value = widthNotVisible;
            }

            if (MathUtilities.AreClose(value, _horizontalOffset))
            {
                return;
            }

            if (_hScrollBar != null && !MathUtilities.AreClose(value, _hScrollBar.Value))
            {
                _hScrollBar.Value = value;
            }

            _horizontalOffset = value;

            DisplayData.FirstDisplayedScrollingCol = ComputeFirstVisibleScrollingColumn();
            // update the lastTotallyDisplayedScrollingCol
            ComputeDisplayedColumns();
        }
    }

    private int FirstDisplayedNonFillerColumnIndex
    {
        get
        {
            DataGridColumn? column = ColumnsInternal.FirstVisibleNonFillerColumn;
            if (column != null)
            {
                if (column.IsFrozen)
                {
                    return column.Index;
                }

                if (DisplayData.FirstDisplayedScrollingCol >= column.Index)
                {
                    return DisplayData.FirstDisplayedScrollingCol;
                }

                return column.Index;
            }

            return -1;
        }
    }

    internal int EditingColumnIndex { get; private set; }

    #endregion

    private DataGridColumnHeadersPresenter? _columnHeadersPresenter;
    private DataGridGroupColumnHeadersPresenter? _groupColumnHeadersPresenter;
    private DataGridColumnDraggingOverIndicator? _dataGridDraggingOverIndicator;

    protected virtual void NotifyColumnDisplayIndexChanged(DataGridColumnEventArgs e)
    {
        ColumnDisplayIndexChanged?.Invoke(this, e);
    }

    protected internal virtual void NotifyColumnReordered(DataGridColumnEventArgs e)
    {
        EnsureVerticalGridLines();
        ColumnReordered?.Invoke(this, e);
    }

    protected internal virtual void NotifyColumnReordering(DataGridColumnReorderingEventArgs e)
    {
        ColumnReordering?.Invoke(this, e);
    }
    
    protected internal virtual void NotifyRowReordered(DataGridRowEventArgs e)
    {
        RowReordered?.Invoke(this, e);
    }

    protected internal virtual void NotifyRowReordering(DataGridRowReorderingEventArgs e)
    {
        RowReordering?.Invoke(this, e);
    }
    
    protected internal virtual void NotifyColumnDraggingOver(DataGridColumnDraggingOverEventArgs e)
    {
        if (_dataGridDraggingOverIndicator != null)
        {
            if (e.DraggedColumn != null && e.DraggingOverColumn != null && e.DraggedColumn != e.DraggingOverColumn)
            {
                _dataGridDraggingOverIndicator.IsVisible          = true;
                _dataGridDraggingOverIndicator.DraggingOverColumn = e.DraggingOverColumn;
                _dataGridDraggingOverIndicator.DraggedColumn = e.DraggedColumn;
            }
            else
            {
                _dataGridDraggingOverIndicator.IsVisible          = false;
                _dataGridDraggingOverIndicator.DraggingOverColumn = null;
                _dataGridDraggingOverIndicator.DraggedColumn      = null;
            }
        }
   
        ColumnDraggingOver?.Invoke(this, e);
    }

    protected internal virtual void NotifyColumnSorting(DataGridColumnEventArgs e)
    {
        Sorting?.Invoke(this, e);
    }

    protected internal virtual void NotifyColumnFiltering(DataGridColumnEventArgs e)
    {
        Filtering?.Invoke(this, e);
    }

    public void Sort(int columnIndex, ListSortDirection? direction = null)
    {
        if (columnIndex < 0 || columnIndex >= ColumnsInternal.Count)
        {
            throw new ArgumentOutOfRangeException(nameof(columnIndex));
        }

        var column = ColumnsInternal[columnIndex];
        if (column is DataGridFillerColumn)
        {
            throw new ArgumentOutOfRangeException(nameof(columnIndex), "Column cannot be filled column.");
        }

        column.Sort(direction);
    }

    public void ClearSort(int columnIndex)
    {
        if (columnIndex < 0 || columnIndex >= ColumnsInternal.Count)
        {
            throw new ArgumentOutOfRangeException(nameof(columnIndex));
        }

        var column = ColumnsInternal[columnIndex];
        if (column is DataGridFillerColumn)
        {
            throw new ArgumentOutOfRangeException(nameof(columnIndex), "Column cannot be filled column.");
        }

        column.ClearSort();
    }

    public void ClearSort()
    {
        if (WaitForLostFocus(ClearSort))
        {
            return;
        }

        if (CommitEdit(DataGridEditingUnit.Row, exitEditingMode: true))
        {
            Avalonia.Threading.Dispatcher.UIThread.Post(ProcessClearSort);
        }
    }

    internal void ProcessClearSort()
    {
        if (EditingRow == null)
        {
            foreach (var column in ColumnsInternal)
            {
                var ea = new DataGridColumnEventArgs(column);
                NotifyColumnSorting(ea);
            }

            // TODO 我们这里没有判断 HandleColumnSorting 的处理结果，需要评审是否合理
            if (DataConnection.AllowSort && DataConnection.SortDescriptions != null)
            {
                IDataGridCollectionView? collectionView = DataConnection.CollectionView;
                Debug.Assert(collectionView != null);

                using (collectionView.DeferRefresh())
                {
                    DataConnection.SortDescriptions.Clear();
                }
            }
        }
    }

    public void Filter(int columnIndex, List<object> filterValues)
    {
        if (columnIndex < 0 || columnIndex >= ColumnsInternal.Count)
        {
            throw new ArgumentOutOfRangeException(nameof(columnIndex));
        }

        var column = ColumnsInternal[columnIndex];
        if (column is DataGridFillerColumn)
        {
            throw new ArgumentOutOfRangeException(nameof(columnIndex), "Column cannot be filled column.");
        }

        column.Filter(filterValues);
    }

    public void ClearFilter(int columnIndex)
    {
        if (columnIndex < 0 || columnIndex >= ColumnsInternal.Count)
        {
            throw new ArgumentOutOfRangeException(nameof(columnIndex));
        }

        var column = ColumnsInternal[columnIndex];
        if (column is DataGridFillerColumn)
        {
            throw new ArgumentOutOfRangeException(nameof(columnIndex), "Column cannot be filled column.");
        }

        column.ClearFilter();
    }

    public void ClearFilters()
    {
        if (WaitForLostFocus(ClearFilters))
        {
            return;
        }

        if (CommitEdit(DataGridEditingUnit.Row, exitEditingMode: true))
        {
            Avalonia.Threading.Dispatcher.UIThread.Post(ProcessClearFilters);
        }
    }

    internal void ProcessClearFilters()
    {
        if (EditingRow == null)
        {
            foreach (var column in ColumnsInternal)
            {
                var ea = new DataGridColumnEventArgs(column);
                NotifyColumnFiltering(ea);
            }

            // TODO 我们这里没有判断 HandleColumnSorting 的处理结果，需要评审是否合理
            if (DataConnection.AllowFilter && DataConnection.FilterDescriptions != null)
            {
                IDataGridCollectionView? collectionView = DataConnection.CollectionView;
                Debug.Assert(collectionView != null);

                using (collectionView.DeferRefresh())
                {
                    DataConnection.FilterDescriptions.Clear();
                }
            }
        }
    }

    /// <summary>
    /// Adjusts the widths of all columns with DisplayIndex >= displayIndex such that the total
    /// width is adjusted by the given amount, if possible.  If the total desired adjustment amount
    /// could not be met, the remaining amount of adjustment is returned.
    /// </summary>
    /// <param name="displayIndex">Starting column DisplayIndex.</param>
    /// <param name="amount">Adjustment amount (positive for increase, negative for decrease).</param>
    /// <param name="userInitiated">Whether or not this adjustment was initiated by a user action.</param>
    /// <returns>The remaining amount of adjustment.</returns>
    internal double AdjustColumnWidths(int displayIndex, double amount, bool userInitiated)
    {
        if (!MathUtilities.IsZero(amount))
        {
            if (amount < 0)
            {
                amount = DecreaseColumnWidths(displayIndex, amount, userInitiated);
            }
            else
            {
                amount = IncreaseColumnWidths(displayIndex, amount, userInitiated);
            }
        }

        return amount;
    }

    /// <summary>
    /// Grows an auto-column's width to the desired width.
    /// </summary>
    /// <param name="column">Auto-column to adjust.</param>
    /// <param name="desiredWidth">The new desired width of the column.</param>
    internal void AutoSizeColumn(DataGridColumn column, double desiredWidth)
    {
        Debug.Assert(column.Width.IsAuto || column.Width.IsSizeToCells || column.Width.IsSizeToHeader ||
                     (!UsesStarSizing && column.Width.IsStar));

        // If we're using star sizing and this is the first time we've measured this particular auto-column,
        // we want to allow all rows to get measured before we setup the star widths.  We won't know the final
        // desired value of the column until all rows have been measured.  Because of this, we wait until
        // an Arrange occurs before we adjust star widths.
        if (UsesStarSizing && !column.IsInitialDesiredWidthDetermined)
        {
            AutoSizingColumns = true;
        }

        // Update the column's DesiredValue if it needs to grow to fit the new desired value
        if (desiredWidth > column.Width.DesiredValue || double.IsNaN(column.Width.DesiredValue))
        {
            // If this auto-growth occurs after the column's initial desired width has been determined,
            // then the growth should act like a resize (squish columns to the right).  Otherwise, if
            // this column is newly added, we'll just set its display value directly.
            if (UsesStarSizing && column.IsInitialDesiredWidthDetermined)
            {
                var oldWidth = column.Width;
                column.Resize(oldWidth,
                    new(column.Width.Value, column.Width.UnitType, desiredWidth, desiredWidth),
                    false);
            }
            else
            {
                column.SetWidthInternalNoCallback(new DataGridLength(column.Width.Value, column.Width.UnitType,
                    desiredWidth, desiredWidth));
                HandleColumnWidthChanged(column);
            }
        }
    }

    internal bool ColumnRequiresRightGridLine(DataGridColumn dataGridColumn, bool includeLastRightGridLineWhenPresent)
    {
        return (GridLinesVisibility == DataGridGridLinesVisibility.Vertical ||
                GridLinesVisibility == DataGridGridLinesVisibility.All) &&
               (dataGridColumn != ColumnsInternal.LastVisibleColumn ||
                (includeLastRightGridLineWhenPresent &&
                 ColumnsInternal.FillerColumn != null &&
                 ColumnsInternal.FillerColumn.IsActive));
    }

    internal DataGridColumnCollection CreateColumnsInstance()
    {
        return new DataGridColumnCollection(this);
    }

    /// <summary>
    /// Decreases the widths of all columns with DisplayIndex >= displayIndex such that the total
    /// width is decreased by the given amount, if possible.  If the total desired adjustment amount
    /// could not be met, the remaining amount of adjustment is returned.
    /// </summary>
    /// <param name="displayIndex">Starting column DisplayIndex.</param>
    /// <param name="amount">Amount to decrease (in pixels).</param>
    /// <param name="userInitiated">Whether or not this adjustment was initiated by a user action.</param>
    /// <returns>The remaining amount of adjustment.</returns>
    internal double DecreaseColumnWidths(int displayIndex, double amount, bool userInitiated)
    {
        // 1. Take space from non-star columns with widths larger than desired widths (left to right).
        amount = DecreaseNonStarColumnWidths(displayIndex, c => c.Width.DesiredValue, amount, false, false);

        // 2. Take space from star columns until they reach their min.
        amount = AdjustStarColumnWidths(displayIndex, amount, userInitiated);

        // 3. Take space from non-star columns that have already been initialized, until they reach their min (right to left).
        amount = DecreaseNonStarColumnWidths(displayIndex, c => c.ActualMinWidth, amount, true, false);

        // 4. Take space from all non-star columns until they reach their min, even if they are new (right to left).
        amount = DecreaseNonStarColumnWidths(displayIndex, c => c.ActualMinWidth, amount, true, true);

        return amount;
    }

    internal bool GetColumnReadOnlyState(DataGridColumn dataGridColumn, bool isReadOnly)
    {
        Debug.Assert(dataGridColumn != null);

        if (dataGridColumn is DataGridBoundColumn dataGridBoundColumn &&
            dataGridBoundColumn.Binding is BindingBase binding)
        {
            var path = (binding as Binding)?.Path ?? (binding as CompiledBindingExtension)?.Path.ToString();

            if (string.IsNullOrWhiteSpace(path))
            {
                return true;
            }

            return DataConnection.GetPropertyIsReadOnly(path) || isReadOnly;
        }

        return isReadOnly;
    }

    // Returns the column's width
    internal static double GetEdgedColumnWidth(DataGridColumn dataGridColumn)
    {
        Debug.Assert(dataGridColumn != null);
        return dataGridColumn.ActualWidth;
    }

    /// <summary>
    /// Increases the widths of all columns with DisplayIndex >= displayIndex such that the total
    /// width is increased by the given amount, if possible.  If the total desired adjustment amount
    /// could not be met, the remaining amount of adjustment is returned.
    /// </summary>
    /// <param name="displayIndex">Starting column DisplayIndex.</param>
    /// <param name="amount">Amount of increase (in pixels).</param>
    /// <param name="userInitiated">Whether or not this adjustment was initiated by a user action.</param>
    /// <returns>The remaining amount of adjustment.</returns>
    internal double IncreaseColumnWidths(int displayIndex, double amount, bool userInitiated)
    {
        // 1. Give space to non-star columns that are smaller than their desired widths (left to right).
        amount = IncreaseNonStarColumnWidths(displayIndex, c => c.Width.DesiredValue, amount, false, false);

        // 2. Give space to star columns until they reach their max.
        amount = AdjustStarColumnWidths(displayIndex, amount, userInitiated);

        // 3. Give space to non-star columns that have already been initialized, until they reach their max (right to left).
        amount = IncreaseNonStarColumnWidths(displayIndex, c => c.ActualMaxWidth, amount, true, false);

        // 4. Give space to all non-star columns until they reach their max, even if they are new (right to left).
        amount = IncreaseNonStarColumnWidths(displayIndex, c => c.ActualMaxWidth, amount, true, false);

        return amount;
    }

    internal void HandleClearingColumns()
    {
        // Rows need to be cleared first. There cannot be rows without also having columns.
        ClearRows(false);

        // Removing all the column header cells
        RemoveDisplayedColumnHeaders();

        _horizontalOffset = _negHorizontalOffset = 0;
        if (_hScrollBar != null && _hScrollBar.IsVisible) // 
        {
            _hScrollBar.Value = 0;
        }
    }

    /// <summary>
    /// Invalidates the widths of all columns because the resizing behavior of an individual column has changed.
    /// </summary>
    /// <param name="column">Column with CanUserResize property that has changed.</param>
    internal void HandleColumnCanUserResizeChanged(DataGridColumn column)
    {
        if (column.IsVisible)
        {
            EnsureHorizontalLayout();
        }
    }

    internal void HandleColumnCollectionChangedPostNotification(bool columnsGrew)
    {
        if (columnsGrew &&
            CurrentColumnIndex == -1)
        {
            MakeFirstDisplayedCellCurrentCell();
        }

        if (_autoGeneratingColumnOperationCount == 0)
        {
            EnsureRowsPresenterVisibility();
            InvalidateRowHeightEstimate();
        }
    }

    internal void HandleColumnCollectionChangedPreNotification(bool columnsGrew)
    {
        // dataGridColumn==null means the collection was refreshed.

        if (columnsGrew && _autoGeneratingColumnOperationCount == 0 && ColumnsItemsInternal.Count == 1)
        {
            RefreshRows(false /*recycleRows*/, true /*clearRows*/);
        }
        else
        {
            InvalidateMeasure();
        }
    }

    internal void HandleColumnDisplayIndexChanged(DataGridColumn dataGridColumn)
    {
        Debug.Assert(dataGridColumn != null);
        DataGridColumnEventArgs e = new DataGridColumnEventArgs(dataGridColumn);

        // Call protected method to raise event
        if (dataGridColumn != ColumnsInternal.RowGroupSpacerColumn)
        {
            NotifyColumnDisplayIndexChanged(e);
        }
    }

    internal void HandleColumnDisplayIndexChangedPostNotification()
    {
        // Notifications for adjusted display indexes.
        FlushDisplayIndexChanged(true /*raiseEvent*/);

        // Our displayed columns may have changed so recompute them
        UpdateDisplayedColumns();

        // Invalidate layout
        CorrectColumnFrozenStates();
        EnsureHorizontalLayout();
    }

    internal void HandleColumnDisplayIndexChanging(DataGridColumn targetColumn, int newDisplayIndex)
    {
        Debug.Assert(targetColumn != null);
        Debug.Assert(newDisplayIndex != targetColumn.DisplayIndexWithFiller);

        if (InDisplayIndexAdjustments)
        {
            // We are within columns display indexes adjustments. We do not allow changing display indexes while adjusting them.
            throw DataGridError.DataGrid.CannotChangeColumnCollectionWhileAdjustingDisplayIndexes();
        }

        try
        {
            InDisplayIndexAdjustments = true;

            bool trackChange = targetColumn != ColumnsInternal.RowGroupSpacerColumn;

            DataGridColumn? column;
            // Move is legal - let's adjust the affected display indexes.
            if (newDisplayIndex < targetColumn.DisplayIndexWithFiller)
            {
                // DisplayIndex decreases. All columns with newDisplayIndex <= DisplayIndex < targetColumn.DisplayIndex
                // get their DisplayIndex incremented.
                for (int i = newDisplayIndex; i < targetColumn.DisplayIndexWithFiller; i++)
                {
                    column = ColumnsInternal.GetColumnAtDisplayIndex(i);
                    Debug.Assert(column != null);
                    column.DisplayIndexWithFiller += 1;
                    if (trackChange)
                    {
                        column.DisplayIndexHasChanged = true; // OnColumnDisplayIndexChanged needs to be raised later on
                    }
                }
            }
            else
            {
                // DisplayIndex increases. All columns with targetColumn.DisplayIndex < DisplayIndex <= newDisplayIndex
                // get their DisplayIndex decremented.
                for (int i = newDisplayIndex; i > targetColumn.DisplayIndexWithFiller; i--)
                {
                    column = ColumnsInternal.GetColumnAtDisplayIndex(i);
                    Debug.Assert(column != null);
                    column.DisplayIndexWithFiller -= 1;
                    if (trackChange)
                    {
                        column.DisplayIndexHasChanged = true; // OnColumnDisplayIndexChanged needs to be raised later on
                    }
                }
            }

            // Now let's actually change the order of the DisplayIndexMap
            if (targetColumn.DisplayIndexWithFiller != -1)
            {
                ColumnsInternal.DisplayIndexMap.Remove(targetColumn.Index);
            }

            ColumnsInternal.DisplayIndexMap.Insert(newDisplayIndex, targetColumn.Index);
        }
        finally
        {
            InDisplayIndexAdjustments = false;
        }

        // Note that displayIndex of moved column is updated by caller.
    }

    internal void HandleColumnBindingChanged(DataGridBoundColumn column)
    {
        // Update Binding in Displayed rows by regenerating the affected elements
        if (_rowsPresenter != null)
        {
            foreach (DataGridRow row in GetAllRows())
            {
                PopulateCellContent(false /*isCellEdited*/, column, row, row.Cells[column.Index]);
            }
        }
    }

    /// <summary>
    /// Adjusts the specified column's width according to its new maximum value.
    /// </summary>
    /// <param name="column">The column to adjust.</param>
    /// <param name="oldValue">The old ActualMaxWidth of the column.</param>
    internal void HandleColumnMaxWidthChanged(DataGridColumn column, double oldValue)
    {
        Debug.Assert(column != null);

        if (column.IsVisible && !MathUtils.AreClose(oldValue, column.ActualMaxWidth))
        {
            DataGridLength oldWitdh = new(oldValue, column.Width.UnitType, column.Width.DesiredValue,
                column.Width.DesiredValue);
            if (column.ActualMaxWidth < column.Width.DisplayValue)
            {
                // If the maximum width has caused the column to decrease in size, try first to resize
                // the columns to the right to make up for the difference in width, but don't limit the column's
                // final display value to how much they could be resized.
                AdjustColumnWidths(column.DisplayIndex + 1, column.Width.DisplayValue - column.ActualMaxWidth, false);
                column.SetWidthDisplayValue(column.ActualMaxWidth);
            }
            else if (MathUtils.AreClose(column.Width.DisplayValue, oldValue) &&
                     column.Width.DesiredValue > column.Width.DisplayValue)
            {
                // If the column was previously limited by its maximum value but has more room now, 
                // attempt to resize the column to its desired width.
                column.Resize(oldWitdh,
                    new(column.Width.Value, column.Width.UnitType, column.Width.DesiredValue,
                        column.Width.DesiredValue),
                    false);
            }

            HandleColumnWidthChanged(column);
        }
    }

    /// <summary>
    /// Adjusts the specified column's width according to its new minimum value.
    /// </summary>
    /// <param name="column">The column to adjust.</param>
    /// <param name="oldValue">The old ActualMinWidth of the column.</param>
    internal void HandleColumnMinWidthChanged(DataGridColumn column, double oldValue)
    {
        Debug.Assert(column != null);

        if (column.IsVisible && !MathUtils.AreClose(oldValue, column.ActualMinWidth))
        {
            DataGridLength oldWitdh = new(oldValue, column.Width.UnitType, column.Width.DesiredValue,
                column.Width.DesiredValue);
            if (column.ActualMinWidth > column.Width.DisplayValue)
            {
                // If the minimum width has caused the column to increase in size, try first to resize
                // the columns to the right to make up for the difference in width, but don't limit the column's
                // final display value to how much they could be resized.
                AdjustColumnWidths(column.DisplayIndex + 1, column.Width.DisplayValue - column.ActualMinWidth, false);
                column.SetWidthDisplayValue(column.ActualMinWidth);
            }
            else if (MathUtils.AreClose(column.Width.DisplayValue, oldValue) &&
                     column.Width.DesiredValue < column.Width.DisplayValue)
            {
                // If the column was previously limited by its minimum value but but can be smaller now, 
                // attempt to resize the column to its desired width.
                column.Resize(oldWitdh,
                    new(column.Width.Value, column.Width.UnitType, column.Width.DesiredValue,
                        column.Width.DesiredValue),
                    false);
            }

            HandleColumnWidthChanged(column);
        }
    }

    internal void HandleColumnReadOnlyStateChanging(DataGridColumn dataGridColumn, bool isReadOnly)
    {
        Debug.Assert(dataGridColumn != null);
        if (isReadOnly && CurrentColumnIndex == dataGridColumn.Index)
        {
            // Edited column becomes read-only. Exit editing mode.
            if (!EndCellEdit(DataGridEditAction.Commit, true /*exitEditingMode*/, ContainsFocus /*keepFocus*/,
                    true /*raiseEvents*/))
            {
                EndCellEdit(DataGridEditAction.Cancel, true /*exitEditingMode*/, ContainsFocus /*keepFocus*/,
                    false /*raiseEvents*/);
            }
        }
    }

    internal void HandleColumnVisibleStateChanged(DataGridColumn updatedColumn)
    {
        Debug.Assert(updatedColumn != null);

        CorrectColumnFrozenStates();
        UpdateDisplayedColumns();
        EnsureRowsPresenterVisibility();
        EnsureHorizontalLayout();
        InvalidateColumnHeadersMeasure();

        if (updatedColumn.IsVisible &&
            ColumnsInternal.VisibleColumnCount == 1 && CurrentColumnIndex == -1)
        {
            Debug.Assert(SelectedIndex == DataConnection.IndexOf(SelectedItem));
            if (SelectedIndex != -1)
            {
                SetAndSelectCurrentCell(updatedColumn.Index, SelectedIndex, true /*forceCurrentCellSelection*/);
            }
            else
            {
                MakeFirstDisplayedCellCurrentCell();
            }
        }

        // We need to explicitly collapse the cells of the invisible column because layout only goes through
        // visible ones
        ColumnHeaders?.InvalidateChildIndex();
        foreach (var row in GetAllRows())
        {
            row.Cells[updatedColumn.Index].IsVisible = updatedColumn.IsVisible;
            row.InvalidateCellsIndex();
        }
    }

    internal void HandleColumnVisibleStateChanging(DataGridColumn targetColumn)
    {
        Debug.Assert(targetColumn != null);

        if (targetColumn.IsVisible &&
            CurrentColumn == targetColumn)
        {
            // Column of the current cell is made invisible. Trying to move the current cell to a neighbor column. May throw an exception.
            DataGridColumn? dataGridColumn = ColumnsInternal.GetNextVisibleColumn(targetColumn);
            if (dataGridColumn == null)
            {
                dataGridColumn = ColumnsInternal.GetPreviousVisibleNonFillerColumn(targetColumn);
            }

            if (dataGridColumn == null)
            {
                SetCurrentCellCore(-1, -1);
            }
            else
            {
                SetCurrentCellCore(dataGridColumn.Index, CurrentSlot);
            }
        }
    }

    internal void HandleColumnWidthChanged(DataGridColumn updatedColumn)
    {
        Debug.Assert(updatedColumn != null);
        if (updatedColumn.IsVisible)
        {
            EnsureHorizontalLayout();
        }
    }

    internal void HandleFillerColumnWidthNeeded(double finalWidth)
    {
        DataGridFillerColumn? fillerColumn      = ColumnsInternal.FillerColumn;
        double                totalColumnsWidth = ColumnsInternal.VisibleEdgedColumnsWidth + ActualRowHeaderWidth;
        Debug.Assert(fillerColumn != null);
        if (finalWidth - totalColumnsWidth > LayoutHelper.LayoutEpsilon)
        {
            fillerColumn.FillerWidth = finalWidth - totalColumnsWidth;
        }
        else
        {
            fillerColumn.FillerWidth = 0;
        }
    }

    internal void HandleInsertedColumnPostNotification(DataGridCellCoordinates newCurrentCellCoordinates,
                                                       int newDisplayIndex)
    {
        // Update current cell if needed
        if (newCurrentCellCoordinates.ColumnIndex != -1)
        {
            Debug.Assert(CurrentColumnIndex == -1);
            SetAndSelectCurrentCell(newCurrentCellCoordinates.ColumnIndex,
                newCurrentCellCoordinates.Slot,
                ColumnsInternal.VisibleColumnCount == 1 /*forceCurrentCellSelection*/);
            
            if (IsFrozenColumnIndex(newDisplayIndex))
            {
                CorrectColumnFrozenStates();
            }
        }
    }

    internal bool IsFrozenColumnIndex(int index)
    {
        if (index < LeftFrozenColumnCountWithFiller)
        {
            return true;
        }

        var displayColumnCount = ColumnsInternal.GetDisplayedColumnCount();
        if (index >= displayColumnCount - RightFrozenColumnCount && index < displayColumnCount)
        {
            return true;
        }

        return false;
    }

    internal void HandleInsertedColumnPreNotification(DataGridColumn insertedColumn)
    {
        // Fix the Index of all following columns
        CorrectColumnIndexesAfterInsertion(insertedColumn, 1);

        Debug.Assert(insertedColumn.Index >= 0);
        Debug.Assert(insertedColumn.Index < ColumnsItemsInternal.Count);
        Debug.Assert(insertedColumn.OwningGrid == this);

        CorrectColumnDisplayIndexesAfterInsertion(insertedColumn);

        InsertDisplayedColumnHeader(insertedColumn);

        // Insert the missing data cells
        if (SlotCount > 0)
        {
            int newColumnCount = ColumnsItemsInternal.Count;

            foreach (DataGridRow row in GetAllRows())
            {
                if (row.Cells.Count < newColumnCount)
                {
                    AddNewCellPrivate(row, insertedColumn);
                }
            }
        }

        if (insertedColumn.IsVisible)
        {
            EnsureHorizontalLayout();
        }

        if (insertedColumn is DataGridBoundColumn boundColumn && !boundColumn.IsAutoGenerated)
        {
            boundColumn.SetHeaderFromBinding();
        }
    }

    internal DataGridCellCoordinates HandleInsertingColumn(int columnIndexInserted, DataGridColumn insertColumn)
    {
        DataGridCellCoordinates newCurrentCellCoordinates;
        Debug.Assert(insertColumn != null);

        if (insertColumn.OwningGrid != null && insertColumn != ColumnsInternal.RowGroupSpacerColumn)
        {
            throw DataGridError.DataGrid.ColumnCannotBeReassignedToDifferentDataGrid();
        }

        // Reset current cell if there is one, no matter the relative position of the columns involved
        if (CurrentColumnIndex != -1)
        {
            _temporarilyResetCurrentCell = true;
            newCurrentCellCoordinates = new DataGridCellCoordinates(
                columnIndexInserted <= CurrentColumnIndex ? CurrentColumnIndex + 1 : CurrentColumnIndex,
                CurrentSlot);
            ResetCurrentCellCore();
        }
        else
        {
            newCurrentCellCoordinates = new DataGridCellCoordinates(-1, -1);
        }

        return newCurrentCellCoordinates;
    }

    internal void HandleRemovedColumnPostNotification(DataGridCellCoordinates newCurrentCellCoordinates)
    {
        // Update current cell if needed
        if (newCurrentCellCoordinates.ColumnIndex != -1)
        {
            Debug.Assert(CurrentColumnIndex == -1);
            SetAndSelectCurrentCell(newCurrentCellCoordinates.ColumnIndex, newCurrentCellCoordinates.Slot,
                false /*forceCurrentCellSelection*/);
        }
    }

    internal void HandleRemovedColumnPreNotification(DataGridColumn removedColumn)
    {
        Debug.Assert(removedColumn.Index >= 0);
        Debug.Assert(removedColumn.OwningGrid == null);

        // Intentionally keep the DisplayIndex intact after detaching the column.
        CorrectColumnIndexesAfterDeletion(removedColumn);

        CorrectColumnDisplayIndexesAfterDeletion(removedColumn);

        // If the detached column was frozen, a new column needs to take its place
        if (removedColumn.IsFrozen)
        {
            removedColumn.IsFrozen = false;
            CorrectColumnFrozenStates();
        }

        UpdateDisplayedColumns();

        // Fix the existing rows by removing cells at correct index
        int newColumnCount = ColumnsItemsInternal.Count;

        if (_rowsPresenter != null)
        {
            foreach (DataGridRow row in GetAllRows())
            {
                if (row.Cells.Count > newColumnCount)
                {
                    row.Cells.RemoveAt(removedColumn.Index);
                }
            }

            _rowsPresenter.InvalidateArrange();
        }

        RemoveDisplayedColumnHeader(removedColumn);
    }

    internal DataGridCellCoordinates HandleRemovingColumn(DataGridColumn dataGridColumn)
    {
        Debug.Assert(dataGridColumn != null);
        Debug.Assert(dataGridColumn.Index >= 0 && dataGridColumn.Index < ColumnsItemsInternal.Count);

        DataGridCellCoordinates newCurrentCellCoordinates;

        _temporarilyResetCurrentCell = false;
        int columnIndex = dataGridColumn.Index;

        // Reset the current cell's address if there is one.
        if (CurrentColumnIndex != -1)
        {
            int newCurrentColumnIndex = CurrentColumnIndex;
            if (columnIndex == newCurrentColumnIndex)
            {
                DataGridColumn? dataGridColumnNext =
                    ColumnsInternal.GetNextVisibleColumn(ColumnsItemsInternal[columnIndex]);
                if (dataGridColumnNext != null)
                {
                    if (dataGridColumnNext.Index > columnIndex)
                    {
                        newCurrentColumnIndex = dataGridColumnNext.Index - 1;
                    }
                    else
                    {
                        newCurrentColumnIndex = dataGridColumnNext.Index;
                    }
                }
                else
                {
                    DataGridColumn? dataGridColumnPrevious =
                        ColumnsInternal.GetPreviousVisibleNonFillerColumn(ColumnsItemsInternal[columnIndex]);
                    if (dataGridColumnPrevious != null)
                    {
                        if (dataGridColumnPrevious.Index > columnIndex)
                        {
                            newCurrentColumnIndex = dataGridColumnPrevious.Index - 1;
                        }
                        else
                        {
                            newCurrentColumnIndex = dataGridColumnPrevious.Index;
                        }
                    }
                    else
                    {
                        newCurrentColumnIndex = -1;
                    }
                }
            }
            else if (columnIndex < newCurrentColumnIndex)
            {
                newCurrentColumnIndex--;
            }

            newCurrentCellCoordinates = new DataGridCellCoordinates(newCurrentColumnIndex,
                (newCurrentColumnIndex == -1) ? -1 : CurrentSlot);
            if (columnIndex == CurrentColumnIndex)
            {
                // If the commit fails, force a cancel edit
                if (!CommitEdit(DataGridEditingUnit.Row, false /*exitEditingMode*/))
                {
                    CancelEdit(DataGridEditingUnit.Row, false /*raiseEvents*/);
                }

                bool success = SetCurrentCellCore(-1, -1);
                Debug.Assert(success);
            }
            else
            {
                // Underlying data of deleted column is gone. It cannot be accessed anymore.
                // Do not end editing mode so that CellValidation doesn't get raised, since that event needs the current formatted value.
                _temporarilyResetCurrentCell = true;
                bool success = SetCurrentCellCore(-1, -1);
                Debug.Assert(success);
            }
        }
        else
        {
            newCurrentCellCoordinates = new DataGridCellCoordinates(-1, -1);
        }

        // If the last column is removed, delete all the rows first.
        if (ColumnsItemsInternal.Count == 1)
        {
            ClearRows(false);
        }

        // Is deleted column scrolled off screen?
        if (dataGridColumn.IsVisible &&
            !dataGridColumn.IsFrozen &&
            DisplayData.FirstDisplayedScrollingCol >= 0)
        {
            // Deleted column is part of scrolling columns.
            if (DisplayData.FirstDisplayedScrollingCol == dataGridColumn.Index)
            {
                // Deleted column is first scrolling column
                _horizontalOffset    -= _negHorizontalOffset;
                _negHorizontalOffset =  0;
            }
            else if (!ColumnsInternal.DisplayInOrder(DisplayData.FirstDisplayedScrollingCol, dataGridColumn.Index))
            {
                // Deleted column is displayed before first scrolling column
                Debug.Assert(_horizontalOffset >= GetEdgedColumnWidth(dataGridColumn));
                _horizontalOffset -= GetEdgedColumnWidth(dataGridColumn);
            }

            if (_hScrollBar != null && _hScrollBar.IsVisible) 
            {
                _hScrollBar.Value = _horizontalOffset;
            }
        }

        return newCurrentCellCoordinates;
    }

    /// <summary>
    /// Called when a column property changes, and its cells need to 
    /// adjust that that column change.
    /// </summary>
    internal void RefreshColumnElements(DataGridColumn dataGridColumn, string propertyName)
    {
        Debug.Assert(dataGridColumn != null);

        // Take care of the non-displayed loaded rows
        for (int index = 0; index < _loadedRows.Count;)
        {
            DataGridRow dataGridRow = _loadedRows[index];
            Debug.Assert(dataGridRow != null);
            if (!IsSlotVisible(dataGridRow.Slot))
            {
                RefreshCellElement(dataGridColumn, dataGridRow, propertyName);
            }

            index++;
        }

        // Take care of the displayed rows
        if (_rowsPresenter != null)
        {
            foreach (DataGridRow row in GetAllRows())
            {
                RefreshCellElement(dataGridColumn, row, propertyName);
            }

            // This update could change layout so we need to update our estimate and invalidate
            InvalidateRowHeightEstimate();
            InvalidateMeasure();
        }
    }

    /// <summary>
    /// Adjusts the widths of all star columns with DisplayIndex >= displayIndex such that the total
    /// width is adjusted by the given amount, if possible.  If the total desired adjustment amount
    /// could not be met, the remaining amount of adjustment is returned.
    /// </summary>
    /// <param name="displayIndex">Starting column DisplayIndex.</param>
    /// <param name="adjustment">Adjustment amount (positive for increase, negative for decrease).</param>
    /// <param name="userInitiated">Whether or not this adjustment was initiated by a user action.</param>
    /// <returns>The remaining amount of adjustment.</returns>
    private double AdjustStarColumnWidths(int displayIndex, double adjustment, bool userInitiated)
    {
        double remainingAdjustment = adjustment;
        if (MathUtilities.IsZero(remainingAdjustment))
        {
            return remainingAdjustment;
        }

        bool increase = remainingAdjustment > 0;

        // Make an initial pass through the star columns to total up some values.
        bool                 scaleStarWeights           = false;
        double               totalStarColumnsWidth      = 0;
        double               totalStarColumnsWidthLimit = 0;
        double               totalStarWeights           = 0;
        List<DataGridColumn> starColumns                = new List<DataGridColumn>();
        foreach (DataGridColumn column in ColumnsInternal.GetDisplayedColumns(c =>
                     c.Width.IsStar && c.IsVisible && (c.ActualCanUserResize || !userInitiated)))
        {
            if (column.DisplayIndex < displayIndex)
            {
                scaleStarWeights = true;
                continue;
            }

            starColumns.Add(column);
            totalStarWeights           += column.Width.Value;
            totalStarColumnsWidth      += column.Width.DisplayValue;
            totalStarColumnsWidthLimit += increase ? column.ActualMaxWidth : column.ActualMinWidth;
        }

        // Set the new desired widths according to how much all the star columns can be adjusted without any
        // of them being limited by their minimum or maximum widths (as that would distort their ratios).
        double adjustmentLimit = totalStarColumnsWidthLimit - totalStarColumnsWidth;
        adjustmentLimit = increase ? Math.Min(adjustmentLimit, adjustment) : Math.Max(adjustmentLimit, adjustment);
        foreach (DataGridColumn starColumn in starColumns)
        {
            starColumn.SetWidthDesiredValue((totalStarColumnsWidth + adjustmentLimit) * starColumn.Width.Value /
                                            totalStarWeights);
        }

        // Adjust the star column widths first towards their desired values, and then towards their limits.
        remainingAdjustment =
            AdjustStarColumnWidths(displayIndex, remainingAdjustment, userInitiated, c => c.Width.DesiredValue);
        remainingAdjustment = AdjustStarColumnWidths(displayIndex, remainingAdjustment, userInitiated,
            c => increase ? c.ActualMaxWidth : c.ActualMinWidth);

        // Set the new star value weights according to how much the total column widths have changed.
        // Only do this if there were other star columns to the left, though.  If there weren't any then that means
        // all the star columns were adjusted at the same time, and therefore, their ratios have not changed.
        if (scaleStarWeights)
        {
            double starRatio = (totalStarColumnsWidth + adjustment - remainingAdjustment) / totalStarColumnsWidth;
            foreach (DataGridColumn starColumn in starColumns)
            {
                starColumn.SetWidthStarValue(Math.Min(double.MaxValue, starRatio * starColumn.Width.Value));
            }
        }

        return remainingAdjustment;
    }

    /// <summary>
    /// Adjusts the widths of all star columns with DisplayIndex >= displayIndex such that the total
    /// width is adjusted by the given amount, if possible.  If the total desired adjustment amount
    /// could not be met, the remaining amount of adjustment is returned.  The columns will stop adjusting
    /// once they hit their target widths.
    /// </summary>
    /// <param name="displayIndex">Starting column DisplayIndex.</param>
    /// <param name="remainingAdjustment">Adjustment amount (positive for increase, negative for decrease).</param>
    /// <param name="userInitiated">Whether or not this adjustment was initiated by a user action.</param>
    /// <param name="targetWidth">The target width of the column.</param>
    /// <returns>The remaining amount of adjustment.</returns>
    private double AdjustStarColumnWidths(int displayIndex, double remainingAdjustment, bool userInitiated,
                                          Func<DataGridColumn, double> targetWidth)
    {
        if (MathUtilities.IsZero(remainingAdjustment))
        {
            return remainingAdjustment;
        }

        bool increase = remainingAdjustment > 0;

        double totalStarWeights      = 0d;
        double totalStarColumnsWidth = 0d;

        // Order the star columns according to which one will hit their target width (or min/max limit) first.
        // Each KeyValuePair represents a column (as the key) and an ordering factor (as the value).  The ordering factor
        // is computed based on the distance from each column's current display width to its target width.  Because each column
        // could have different star ratios, though, this distance is then adjusted according to its star value.  A column with
        // a larger star value, for example, will change size more rapidly than a column with a lower star value.
        List<KeyValuePair<DataGridColumn, double>> starColumnPairs = new List<KeyValuePair<DataGridColumn, double>>();
        foreach (DataGridColumn column in ColumnsInternal.GetDisplayedColumns(c =>
                     c.Width.IsStar && c.DisplayIndex >= displayIndex && c.IsVisible && c.Width.Value > 0 &&
                     (c.ActualCanUserResize || !userInitiated)))
        {
            int insertIndex = 0;
            double distanceToTarget =
                Math.Min(column.ActualMaxWidth, Math.Max(targetWidth(column), column.ActualMinWidth)) -
                column.Width.DisplayValue;
            double factor = (increase ? Math.Max(0, distanceToTarget) : Math.Min(0, distanceToTarget)) /
                            column.Width.Value;
            foreach (KeyValuePair<DataGridColumn, double> starColumnPair in starColumnPairs)
            {
                if (increase ? factor <= starColumnPair.Value : factor >= starColumnPair.Value)
                {
                    break;
                }

                insertIndex++;
            }

            starColumnPairs.Insert(insertIndex, new KeyValuePair<DataGridColumn, double>(column, factor));
            totalStarWeights      += column.Width.Value;
            totalStarColumnsWidth += column.Width.DisplayValue;
        }

        // Adjust the column widths one at a time until they either hit their individual target width
        // or the total remaining amount to adjust has been depleted.
        foreach (KeyValuePair<DataGridColumn, double> starColumnPair in starColumnPairs)
        {
            double distanceToTarget  = starColumnPair.Value * starColumnPair.Key.Width.Value;
            double distanceAvailable = (starColumnPair.Key.Width.Value * remainingAdjustment) / totalStarWeights;
            double adjustment = increase
                ? Math.Min(distanceToTarget, distanceAvailable)
                : Math.Max(distanceToTarget, distanceAvailable);

            remainingAdjustment -= adjustment;
            totalStarWeights    -= starColumnPair.Key.Width.Value;
            starColumnPair.Key.SetWidthDisplayValue(Math.Max(MinimumStarColumnWidth,
                starColumnPair.Key.Width.DisplayValue + adjustment));
        }

        return remainingAdjustment;
    }

    private bool ComputeDisplayedColumns()
    {
        bool   invalidate                 = false;
        int    numVisibleScrollingCols    = 0;
        int    visibleScrollingColumnsTmp = 0;
        double displayWidth               = CellsWidth;
        double cx                         = 0;
        int    firstDisplayedFrozenCol    = -1;
        int    firstDisplayedScrollingCol = DisplayData.FirstDisplayedScrollingCol;

        // the same problem with negative numbers:
        // if the width passed in is negative, then return 0
        if (displayWidth <= 0 || ColumnsInternal.VisibleColumnCount == 0)
        {
            DisplayData.FirstDisplayedScrollingCol       = -1;
            DisplayData.LastTotallyDisplayedScrollingCol = -1;
            return invalidate;
        }

        foreach (DataGridColumn dataGridColumn in ColumnsInternal.GetVisibleFrozenColumns())
        {
            if (firstDisplayedFrozenCol == -1)
            {
                firstDisplayedFrozenCol = dataGridColumn.Index;
            }

            cx += GetEdgedColumnWidth(dataGridColumn);
            if (cx >= displayWidth)
            {
                break;
            }
        }

        Debug.Assert(cx <= ColumnsInternal.GetVisibleFrozenEdgedColumnsWidth());

        if (cx < displayWidth && firstDisplayedScrollingCol >= 0)
        {
            DataGridColumn? dataGridColumn = ColumnsItemsInternal[firstDisplayedScrollingCol];
            if (dataGridColumn.IsFrozen)
            {
                dataGridColumn       = ColumnsInternal.FirstVisibleScrollingColumn;
                _negHorizontalOffset = 0;
                if (dataGridColumn == null)
                {
                    DisplayData.FirstDisplayedScrollingCol = DisplayData.LastTotallyDisplayedScrollingCol = -1;
                    return invalidate;
                }
                firstDisplayedScrollingCol = dataGridColumn.Index;
            }

            cx -= _negHorizontalOffset;
            while (cx < displayWidth && dataGridColumn != null)
            {
                cx += GetEdgedColumnWidth(dataGridColumn);
                visibleScrollingColumnsTmp++;
                dataGridColumn = ColumnsInternal.GetNextVisibleColumn(dataGridColumn);
            }

            numVisibleScrollingCols = visibleScrollingColumnsTmp;

            // if we inflate the data area then we paint columns to the left of firstDisplayedScrollingCol
            if (cx < displayWidth)
            {
                Debug.Assert(firstDisplayedScrollingCol >= 0);
                //first minimize value of _negHorizontalOffset
                if (_negHorizontalOffset > 0)
                {
                    invalidate = true;
                    if (displayWidth - cx > _negHorizontalOffset)
                    {
                        cx                += _negHorizontalOffset;
                        _horizontalOffset -= _negHorizontalOffset;
                        if (_horizontalOffset < LayoutHelper.LayoutEpsilon)
                        {
                            // Snap to zero to avoid trying to partially scroll in first scrolled off column below
                            _horizontalOffset = 0;
                        }

                        _negHorizontalOffset = 0;
                    }
                    else
                    {
                        _horizontalOffset    -= displayWidth - cx;
                        _negHorizontalOffset -= displayWidth - cx;
                        cx                   =  displayWidth;
                    }

                    // Make sure the HorizontalAdjustment is not greater than the new HorizontalOffset
                    // since it would cause an assertion failure in DataGridCellsPresenter.ShouldDisplayCell
                    // called by DataGridCellsPresenter.MeasureOverride.
                    HorizontalAdjustment = Math.Min(HorizontalAdjustment, _horizontalOffset);
                }

                // second try to scroll entire columns
                if (cx < displayWidth && _horizontalOffset > 0)
                {
                    Debug.Assert(_negHorizontalOffset == 0);
                    dataGridColumn =
                        ColumnsInternal.GetPreviousVisibleScrollingColumn(
                            ColumnsItemsInternal[firstDisplayedScrollingCol]);
                    while (dataGridColumn != null && cx + GetEdgedColumnWidth(dataGridColumn) <= displayWidth)
                    {
                        cx += GetEdgedColumnWidth(dataGridColumn);
                        visibleScrollingColumnsTmp++;
                        invalidate                 =  true;
                        firstDisplayedScrollingCol =  dataGridColumn.Index;
                        _horizontalOffset          -= GetEdgedColumnWidth(dataGridColumn);
                        dataGridColumn             =  ColumnsInternal.GetPreviousVisibleScrollingColumn(dataGridColumn);
                    }
                }

                // third try to partially scroll in first scrolled off column
                if (cx < displayWidth && _horizontalOffset > 0)
                {
                    Debug.Assert(_negHorizontalOffset == 0);
                    dataGridColumn =
                        ColumnsInternal.GetPreviousVisibleScrollingColumn(
                            ColumnsItemsInternal[firstDisplayedScrollingCol]);
                    Debug.Assert(dataGridColumn != null);
                    Debug.Assert(GetEdgedColumnWidth(dataGridColumn) > displayWidth - cx);
                    firstDisplayedScrollingCol =  dataGridColumn.Index;
                    _negHorizontalOffset       =  GetEdgedColumnWidth(dataGridColumn) - displayWidth + cx;
                    _horizontalOffset          -= displayWidth - cx;
                    visibleScrollingColumnsTmp++;
                    invalidate = true;
                    cx         = displayWidth;
                    // 我们不需要那么高的精度
                    Debug.Assert(DataGridHelper.AreEqualAt3Decimals(_negHorizontalOffset,
                        GetNegHorizontalOffsetFromHorizontalOffset(_horizontalOffset)));
                }

                // update the number of visible columns to the new reality
                Debug.Assert(numVisibleScrollingCols <= visibleScrollingColumnsTmp,
                    "the number of displayed columns can only grow");
                numVisibleScrollingCols = visibleScrollingColumnsTmp;
            }

            int jumpFromFirstVisibleScrollingCol = numVisibleScrollingCols - 1;
            if (cx > displayWidth)
            {
                jumpFromFirstVisibleScrollingCol--;
            }

            Debug.Assert(jumpFromFirstVisibleScrollingCol >= -1);

            if (jumpFromFirstVisibleScrollingCol < 0)
            {
                DisplayData.LastTotallyDisplayedScrollingCol = -1; // no totally visible scrolling column at all
            }
            else
            {
                Debug.Assert(firstDisplayedScrollingCol >= 0);
                dataGridColumn = ColumnsItemsInternal[firstDisplayedScrollingCol];
                for (int jump = 0; jump < jumpFromFirstVisibleScrollingCol; jump++)
                {
                    dataGridColumn = ColumnsInternal.GetNextVisibleColumn(dataGridColumn);
                    Debug.Assert(dataGridColumn != null);
                }

                DisplayData.LastTotallyDisplayedScrollingCol = dataGridColumn.Index;
            }
        }
        else
        {
            DisplayData.LastTotallyDisplayedScrollingCol = -1;
        }

        DisplayData.FirstDisplayedScrollingCol = firstDisplayedScrollingCol;

        return invalidate;
    }

    private int ComputeFirstVisibleScrollingColumn()
    {
        if (ColumnsInternal.GetVisibleLeftFrozenEdgedColumnsWidth() >= CellsWidth)
        {
            // Not enough room for scrolling columns.
            _negHorizontalOffset = 0;
            return -1;
        }

        DataGridColumn? dataGridColumn = ColumnsInternal.FirstVisibleScrollingColumn;

        if (_horizontalOffset == 0)
        {
            _negHorizontalOffset = 0;
            return (dataGridColumn == null) ? -1 : dataGridColumn.Index;
        }

        double cx = 0;
        while (dataGridColumn != null)
        {
            cx += GetEdgedColumnWidth(dataGridColumn);
            if (cx > _horizontalOffset)
            {
                break;
            }

            dataGridColumn = ColumnsInternal.GetNextVisibleColumn(dataGridColumn);
        }

        if (dataGridColumn == null)
        {
            Debug.Assert(cx <= _horizontalOffset);
            dataGridColumn = ColumnsInternal.FirstVisibleScrollingColumn;
            if (dataGridColumn == null)
            {
                _negHorizontalOffset = 0;
                return -1;
            }

            if (!MathUtils.AreClose(_negHorizontalOffset, _horizontalOffset))
            {
                _negHorizontalOffset = 0;
            }

            return dataGridColumn.Index;
        }

        _negHorizontalOffset = GetEdgedColumnWidth(dataGridColumn) - (cx - _horizontalOffset);
        return dataGridColumn.Index;
    }

    private void CorrectColumnDisplayIndexesAfterDeletion(DataGridColumn deletedColumn)
    {
        // Column indexes have already been adjusted.
        // This column has already been detached and has retained its old Index and DisplayIndex

        Debug.Assert(deletedColumn != null);
        Debug.Assert(deletedColumn.OwningGrid == null);
        Debug.Assert(deletedColumn.Index >= 0);
        Debug.Assert(deletedColumn.DisplayIndexWithFiller >= 0);

        try
        {
            InDisplayIndexAdjustments = true;

            // The DisplayIndex of columns greater than the deleted column need to be decremented,
            // as do the DisplayIndexMap values of modified column Indexes
            DataGridColumn? column;
            ColumnsInternal.DisplayIndexMap.RemoveAt(deletedColumn.DisplayIndexWithFiller);
            for (int displayIndex = 0; displayIndex < ColumnsInternal.DisplayIndexMap.Count; displayIndex++)
            {
                if (ColumnsInternal.DisplayIndexMap[displayIndex] > deletedColumn.Index)
                {
                    ColumnsInternal.DisplayIndexMap[displayIndex]--;
                }

                if (displayIndex >= deletedColumn.DisplayIndexWithFiller)
                {
                    column = ColumnsInternal.GetColumnAtDisplayIndex(displayIndex);
                    Debug.Assert(column != null);
                    column.DisplayIndexWithFiller -= 1;
                    column.DisplayIndexHasChanged =  true; // OnColumnDisplayIndexChanged needs to be raised later on
                }
            }

            // Now raise all the OnColumnDisplayIndexChanged events
            FlushDisplayIndexChanged(true /*raiseEvent*/);
        }
        finally
        {
            InDisplayIndexAdjustments = false;
            FlushDisplayIndexChanged(false /*raiseEvent*/);
        }
    }

    private void CorrectColumnDisplayIndexesAfterInsertion(DataGridColumn insertedColumn)
    {
        Debug.Assert(insertedColumn != null);
        Debug.Assert(insertedColumn.OwningGrid == this);
        if (insertedColumn.DisplayIndexWithFiller == -1 ||
            insertedColumn.DisplayIndexWithFiller >= ColumnsItemsInternal.Count)
        {
            // Developer did not assign a DisplayIndex or picked a large number.
            // Choose the Index as the DisplayIndex.
            insertedColumn.DisplayIndexWithFiller = insertedColumn.Index;
        }

        try
        {
            InDisplayIndexAdjustments = true;

            // The DisplayIndex of columns greater than the inserted column need to be incremented,
            // as do the DisplayIndexMap values of modified column Indexes
            DataGridColumn? column;
            for (int displayIndex = 0; displayIndex < ColumnsInternal.DisplayIndexMap.Count; displayIndex++)
            {
                if (ColumnsInternal.DisplayIndexMap[displayIndex] >= insertedColumn.Index)
                {
                    ColumnsInternal.DisplayIndexMap[displayIndex]++;
                }

                if (displayIndex >= insertedColumn.DisplayIndexWithFiller)
                {
                    column = ColumnsInternal.GetColumnAtDisplayIndex(displayIndex);
                    Debug.Assert(column != null);
                    column.DisplayIndexWithFiller++;
                    column.DisplayIndexHasChanged = true; // OnColumnDisplayIndexChanged needs to be raised later on
                }
            }

            ColumnsInternal.DisplayIndexMap.Insert(insertedColumn.DisplayIndexWithFiller, insertedColumn.Index);

            // Now raise all the OnColumnDisplayIndexChanged events
            FlushDisplayIndexChanged(true /*raiseEvent*/);
        }
        finally
        {
            InDisplayIndexAdjustments = false;
            FlushDisplayIndexChanged(false /*raiseEvent*/);
        }
    }

    private void CorrectColumnFrozenStates()
    {
        int    index                = 0;
        int    displayedColumnCount = ColumnsInternal.GetDisplayedColumnCount();
        foreach (DataGridColumn column in ColumnsInternal.GetDisplayedColumns())
        {
            bool isLeftFrozen  = index < LeftFrozenColumnCountWithFiller;
            bool isRightFrozen = index >= (displayedColumnCount - RightFrozenColumnCount);
            
            if (isLeftFrozen)
            {
                column.IsLeftFrozen = true;
                column.IsRightFrozen = false;
            }
            else if (isRightFrozen)
            {
                column.IsRightFrozen = true;
                column.IsLeftFrozen = false;
            }
            else
            {
                column.IsFrozen = false;
            }

            index++;
        }

        UpdateHorizontalOffset(0);
    }

    private void CorrectColumnIndexesAfterDeletion(DataGridColumn deletedColumn)
    {
        Debug.Assert(deletedColumn != null);
        for (int columnIndex = deletedColumn.Index; columnIndex < ColumnsItemsInternal.Count; columnIndex++)
        {
            ColumnsItemsInternal[columnIndex].Index -= 1;
            Debug.Assert(ColumnsItemsInternal[columnIndex].Index == columnIndex);
        }
    }

    private void CorrectColumnIndexesAfterInsertion(DataGridColumn insertedColumn, int insertionCount)
    {
        Debug.Assert(insertedColumn != null);
        Debug.Assert(insertionCount > 0);
        for (int columnIndex = insertedColumn.Index + insertionCount;
             columnIndex < ColumnsItemsInternal.Count;
             columnIndex++)
        {
            ColumnsItemsInternal[columnIndex].Index = columnIndex;
        }
    }

    /// <summary>
    /// Decreases the width of a non-star column by the given amount, if possible.  If the total desired
    /// adjustment amount could not be met, the remaining amount of adjustment is returned.  The adjustment
    /// stops when the column's target width has been met.
    /// </summary>
    /// <param name="column">Column to adjust.</param>
    /// <param name="targetWidth">The target width of the column (in pixels).</param>
    /// <param name="amount">Amount to decrease (in pixels).</param>
    /// <returns>The remaining amount of adjustment.</returns>
    private static double DecreaseNonStarColumnWidth(DataGridColumn column, double targetWidth, double amount)
    {
        Debug.Assert(amount < 0);
        Debug.Assert(column.Width.UnitType != DataGridLengthUnitType.Star);

        if (MathUtilities.GreaterThanOrClose(targetWidth, column.Width.DisplayValue))
        {
            return amount;
        }

        double adjustment = Math.Max(
            column.ActualMinWidth - column.Width.DisplayValue,
            Math.Max(targetWidth - column.Width.DisplayValue, amount));

        column.SetWidthDisplayValue(column.Width.DisplayValue + adjustment);
        return amount - adjustment;
    }

    /// <summary>
    /// Decreases the widths of all non-star columns with DisplayIndex >= displayIndex such that the total
    /// width is decreased by the given amount, if possible.  If the total desired adjustment amount
    /// could not be met, the remaining amount of adjustment is returned.  The adjustment stops when
    /// the column's target width has been met.
    /// </summary>
    /// <param name="displayIndex">Starting column DisplayIndex.</param>
    /// <param name="targetWidth">The target width of the column (in pixels).</param>
    /// <param name="amount">Amount to decrease (in pixels).</param>
    /// <param name="reverse">Whether or not to reverse the order in which the columns are traversed.</param>
    /// <param name="affectNewColumns">Whether or not to adjust widths of columns that do not yet have their initial desired width.</param>
    /// <returns>The remaining amount of adjustment.</returns>
    private double DecreaseNonStarColumnWidths(int displayIndex, Func<DataGridColumn, double> targetWidth,
                                               double amount, bool reverse, bool affectNewColumns)
    {
        if (MathUtilities.GreaterThanOrClose(amount, 0))
        {
            return amount;
        }

        foreach (DataGridColumn column in ColumnsInternal.GetDisplayedColumns(reverse,
                     column =>
                         column.IsVisible &&
                         column.Width.UnitType != DataGridLengthUnitType.Star &&
                         column.DisplayIndex >= displayIndex &&
                         column.ActualCanUserResize &&
                         (affectNewColumns || column.IsInitialDesiredWidthDetermined)))
        {
            amount = DecreaseNonStarColumnWidth(column, Math.Max(column.ActualMinWidth, targetWidth(column)), amount);
            if (MathUtilities.IsZero(amount))
            {
                break;
            }
        }

        return amount;
    }

    private void FlushDisplayIndexChanged(bool raiseEvent)
    {
        foreach (DataGridColumn column in ColumnsItemsInternal)
        {
            if (column.DisplayIndexHasChanged)
            {
                column.DisplayIndexHasChanged = false;
                if (raiseEvent)
                {
                    Debug.Assert(column != ColumnsInternal.RowGroupSpacerColumn);
                    HandleColumnDisplayIndexChanged(column);
                }
            }
        }
    }

    private bool GetColumnEffectiveReadOnlyState(DataGridColumn? dataGridColumn)
    {
        Debug.Assert(dataGridColumn != null);

        return IsReadOnly || dataGridColumn.IsReadOnly || dataGridColumn is DataGridFillerColumn;
    }

    /// <devdoc>
    ///      Returns the absolute coordinate of the left edge of the given column (including
    ///      the potential gridline - that is the left edge of the gridline is returned). Note that
    ///      the column does not need to be in the display area.
    /// </devdoc>
    private double GetColumnXFromIndex(int index)
    {
        Debug.Assert(index < ColumnsItemsInternal.Count);
        Debug.Assert(ColumnsItemsInternal[index].IsVisible);

        double x = 0;
        foreach (DataGridColumn column in ColumnsInternal.GetVisibleColumns())
        {
            if (index == column.Index)
            {
                break;
            }

            x += GetEdgedColumnWidth(column);
        }

        return x;
    }

    private double GetNegHorizontalOffsetFromHorizontalOffset(double horizontalOffset)
    {
        foreach (DataGridColumn column in ColumnsInternal.GetVisibleScrollingColumns())
        {
            if (GetEdgedColumnWidth(column) > horizontalOffset)
            {
                break;
            }

            horizontalOffset -= GetEdgedColumnWidth(column);
        }

        return horizontalOffset;
    }

    /// <summary>
    /// Increases the width of a non-star column by the given amount, if possible.  If the total desired
    /// adjustment amount could not be met, the remaining amount of adjustment is returned.  The adjustment
    /// stops when the column's target width has been met.
    /// </summary>
    /// <param name="column">Column to adjust.</param>
    /// <param name="targetWidth">The target width of the column (in pixels).</param>
    /// <param name="amount">Amount to increase (in pixels).</param>
    /// <returns>The remaining amount of adjustment.</returns>
    private static double IncreaseNonStarColumnWidth(DataGridColumn column, double targetWidth, double amount)
    {
        Debug.Assert(amount > 0);
        Debug.Assert(column.Width.UnitType != DataGridLengthUnitType.Star);

        if (targetWidth <= column.Width.DisplayValue)
        {
            return amount;
        }

        double adjustment = Math.Min(
            column.ActualMaxWidth - column.Width.DisplayValue,
            Math.Min(targetWidth - column.Width.DisplayValue, amount));

        column.SetWidthDisplayValue(column.Width.DisplayValue + adjustment);
        return amount - adjustment;
    }

    /// <summary>
    /// Increases the widths of all non-star columns with DisplayIndex >= displayIndex such that the total
    /// width is increased by the given amount, if possible.  If the total desired adjustment amount
    /// could not be met, the remaining amount of adjustment is returned.  The adjustment stops when
    /// the column's target width has been met.
    /// </summary>
    /// <param name="displayIndex">Starting column DisplayIndex.</param>
    /// <param name="targetWidth">The target width of the column (in pixels).</param>
    /// <param name="amount">Amount to increase (in pixels).</param>
    /// <param name="reverse">Whether or not to reverse the order in which the columns are traversed.</param>
    /// <param name="affectNewColumns">Whether or not to adjust widths of columns that do not yet have their initial desired width.</param>
    /// <returns>The remaining amount of adjustment.</returns>
    private double IncreaseNonStarColumnWidths(int displayIndex, Func<DataGridColumn, double> targetWidth,
                                               double amount, bool reverse, bool affectNewColumns)
    {
        if (MathUtilities.LessThanOrClose(amount, 0))
        {
            return amount;
        }

        foreach (DataGridColumn column in ColumnsInternal.GetDisplayedColumns(reverse,
                     column =>
                         column.IsVisible &&
                         column.Width.UnitType != DataGridLengthUnitType.Star &&
                         column.DisplayIndex >= displayIndex &&
                         column.ActualCanUserResize &&
                         (affectNewColumns || column.IsInitialDesiredWidthDetermined)))
        {
            amount = IncreaseNonStarColumnWidth(column, Math.Min(column.ActualMaxWidth, targetWidth(column)), amount);
            if (MathUtilities.IsZero(amount))
            {
                break;
            }
        }

        return amount;
    }

    private void InsertDisplayedColumnHeader(DataGridColumn dataGridColumn)
    {
        Debug.Assert(dataGridColumn != null);
        if (_columnHeadersPresenter != null)
        {
            dataGridColumn.HeaderCell.IsVisible = dataGridColumn.IsVisible;
            Debug.Assert(!_columnHeadersPresenter.Children.Contains(dataGridColumn.HeaderCell));
            dataGridColumn.HeaderCell.CanUserSort       = dataGridColumn.CanUserSort;
            dataGridColumn.HeaderCell.CanUserFilter     = dataGridColumn.CanUserFilter;
            dataGridColumn.HeaderCell.ShowSorterTooltip = dataGridColumn.ShowSorterTooltip;
            _columnHeadersPresenter.Children.Insert(dataGridColumn.DisplayIndexWithFiller, dataGridColumn.HeaderCell);
        }
    }

    private void BuildColumnGroupView()
    {
        if (ColumnGroupsInternal.Count > 0)
        {
            foreach (var item in ColumnGroupsInternal)
            {
                if (item is IDataGridColumnGroupItemInternal groupItem)
                {
                    var headerViewItem = new DataGridHeaderViewItem(this);
                    groupItem.GroupHeaderViewItem = headerViewItem;
                    if (groupItem is DataGridColumn column)
                    {
                        headerViewItem.Content                = column.HeaderCell;
                        headerViewItem.OwningColumn           = column;
                        column.HeaderCell.IsSeparatorsVisible = false;
                    }
                    else if (groupItem is DataGridColumnGroupItem columnGroupItem)
                    {
                        headerViewItem.Content = columnGroupItem.HeaderCell;
                    }

                    if (groupItem.GroupChildren.Count > 0)
                    {
                        headerViewItem.IsLeaf = false;
                        foreach (IDataGridColumnGroupItem child in groupItem.GroupChildren)
                        {
                            if (child is IDataGridColumnGroupItemInternal childGroup)
                            {
                                BuildGroupViewItemRecursive(childGroup, 1);
                            }
                        }
                    }
                    else
                    {
                        headerViewItem.IsLeaf = true;
                    }

                    _groupColumnHeadersPresenter?.Children.Add(headerViewItem);
                }
            }
        }
    }

    private void BuildGroupViewItemRecursive(IDataGridColumnGroupItemInternal columnGroupItem, int depth)
    {
        var childHeaderViewItem = new DataGridHeaderViewItem(this);
        columnGroupItem.GroupHeaderViewItem = childHeaderViewItem;
        if (columnGroupItem is DataGridColumn column)
        {
            childHeaderViewItem.Content           = column.HeaderCell;
            column.HeaderCell.IsSeparatorsVisible = false;
        }
        else if (columnGroupItem is DataGridColumnGroupItem groupItem)
        {
            childHeaderViewItem.Content = groupItem.HeaderCell;
        }

        if (columnGroupItem.GroupChildren.Count > 0)
        {
            childHeaderViewItem.IsLeaf = false;
            foreach (var child in columnGroupItem.GroupChildren)
            {
                if (child is IDataGridColumnGroupItemInternal childGroup)
                {
                    BuildGroupViewItemRecursive(childGroup, depth + 1);
                }
            }
        }
        else
        {
            childHeaderViewItem.IsLeaf = true;
        }

        _groupColumnHeadersPresenter?.Children.Add(childHeaderViewItem);
    }

    private static void RefreshCellElement(DataGridColumn dataGridColumn, DataGridRow dataGridRow, string propertyName)
    {
        Debug.Assert(dataGridColumn != null);
        Debug.Assert(dataGridRow != null);

        DataGridCell dataGridCell = dataGridRow.Cells[dataGridColumn.Index];
        Debug.Assert(dataGridCell != null);
        if (dataGridCell.Content is Control element)
        {
            dataGridColumn.RefreshCellContent(element, propertyName);
        }
    }

    private void RemoveAutoGeneratedColumns()
    {
        int index = 0;
        _autoGeneratingColumnOperationCount++;
        try
        {
            while (index < ColumnsInternal.Count)
            {
                // Skip over the user columns
                while (index < ColumnsInternal.Count && !ColumnsInternal[index].IsAutoGenerated)
                {
                    index++;
                }

                // Remove the autogenerated columns
                while (index < ColumnsInternal.Count && ColumnsInternal[index].IsAutoGenerated)
                {
                    ColumnsInternal.RemoveAt(index);
                }
            }

            ColumnsInternal.AutogeneratedColumnCount = 0;
        }
        finally
        {
            _autoGeneratingColumnOperationCount--;
        }
    }

    private bool ScrollColumnIntoView(int columnIndex)
    {
        Debug.Assert(columnIndex >= 0 && columnIndex < ColumnsItemsInternal.Count);

        if (DisplayData.FirstDisplayedScrollingCol != -1 &&
            !ColumnsItemsInternal[columnIndex].IsFrozen &&
            (columnIndex != DisplayData.FirstDisplayedScrollingCol || _negHorizontalOffset > 0))
        {
            int columnsToScroll;
            if (ColumnsInternal.DisplayInOrder(columnIndex, DisplayData.FirstDisplayedScrollingCol))
            {
                columnsToScroll = ColumnsInternal.GetColumnCount(true /* isVisible */, false /* isFrozen */,
                    columnIndex, DisplayData.FirstDisplayedScrollingCol);
                if (_negHorizontalOffset > 0)
                {
                    columnsToScroll++;
                }

                ScrollColumns(-columnsToScroll);
            }
            else if (columnIndex == DisplayData.FirstDisplayedScrollingCol && _negHorizontalOffset > 0)
            {
                ScrollColumns(-1);
            }
            else if (DisplayData.LastTotallyDisplayedScrollingCol == -1 ||
                     (DisplayData.LastTotallyDisplayedScrollingCol != columnIndex &&
                      ColumnsInternal.DisplayInOrder(DisplayData.LastTotallyDisplayedScrollingCol, columnIndex)))
            {
                double xColumnLeftEdge  = GetColumnXFromIndex(columnIndex);
                double xColumnRightEdge = xColumnLeftEdge + GetEdgedColumnWidth(ColumnsItemsInternal[columnIndex]);
                double change           = xColumnRightEdge - HorizontalOffset - CellsWidth;
                double widthRemaining   = change;

                DataGridColumn newFirstDisplayedScrollingCol =
                    ColumnsItemsInternal[DisplayData.FirstDisplayedScrollingCol];
                DataGridColumn? nextColumn = ColumnsInternal.GetNextVisibleColumn(newFirstDisplayedScrollingCol);
                double newColumnWidth = GetEdgedColumnWidth(newFirstDisplayedScrollingCol) - _negHorizontalOffset;
                while (nextColumn != null && widthRemaining >= newColumnWidth)
                {
                    widthRemaining                -= newColumnWidth;
                    newFirstDisplayedScrollingCol = nextColumn;
                    newColumnWidth                = GetEdgedColumnWidth(newFirstDisplayedScrollingCol);
                    nextColumn                    = ColumnsInternal.GetNextVisibleColumn(newFirstDisplayedScrollingCol);
                    _negHorizontalOffset          = 0;
                }

                _negHorizontalOffset                         += widthRemaining;
                DisplayData.LastTotallyDisplayedScrollingCol =  columnIndex;
                if (newFirstDisplayedScrollingCol.Index == columnIndex)
                {
                    _negHorizontalOffset = 0;
                    double frozenColumnWidth = ColumnsInternal.GetVisibleLeftFrozenEdgedColumnsWidth();
                    // If the entire column cannot be displayed, we want to start showing it from its LeftEdge
                    if (newColumnWidth > (CellsWidth - frozenColumnWidth))
                    {
                        DisplayData.LastTotallyDisplayedScrollingCol = -1;
                        change = xColumnLeftEdge - HorizontalOffset - frozenColumnWidth;
                    }
                }

                DisplayData.FirstDisplayedScrollingCol = newFirstDisplayedScrollingCol.Index;

                // At this point DisplayData.FirstDisplayedScrollingColumn and LastDisplayedScrollingColumn 
                // should be correct
                if (change != 0)
                {
                    UpdateHorizontalOffset(HorizontalOffset + change);
                }
            }
        }

        return true;
    }

    private void ScrollColumns(int columns)
    {
        DataGridColumn? newFirstVisibleScrollingCol = null;
        DataGridColumn? dataGridColumnTmp;
        int             colCount = 0;
        if (columns > 0)
        {
            if (DisplayData.LastTotallyDisplayedScrollingCol >= 0)
            {
                dataGridColumnTmp = ColumnsItemsInternal[DisplayData.LastTotallyDisplayedScrollingCol];
                while (colCount < columns && dataGridColumnTmp != null)
                {
                    dataGridColumnTmp = ColumnsInternal.GetNextVisibleColumn(dataGridColumnTmp);
                    colCount++;
                }

                if (dataGridColumnTmp == null)
                {
                    // no more column to display on the right of the last totally seen column
                    return;
                }
            }

            Debug.Assert(DisplayData.FirstDisplayedScrollingCol >= 0);
            dataGridColumnTmp = ColumnsItemsInternal[DisplayData.FirstDisplayedScrollingCol];
            colCount          = 0;
            while (colCount < columns && dataGridColumnTmp != null)
            {
                dataGridColumnTmp = ColumnsInternal.GetNextVisibleColumn(dataGridColumnTmp);
                colCount++;
            }

            newFirstVisibleScrollingCol = dataGridColumnTmp;
        }

        if (columns < 0)
        {
            Debug.Assert(DisplayData.FirstDisplayedScrollingCol >= 0);
            dataGridColumnTmp = ColumnsItemsInternal[DisplayData.FirstDisplayedScrollingCol];
            if (_negHorizontalOffset > 0)
            {
                colCount++;
            }

            while (colCount < -columns && dataGridColumnTmp != null)
            {
                dataGridColumnTmp = ColumnsInternal.GetPreviousVisibleScrollingColumn(dataGridColumnTmp);
                colCount++;
            }

            newFirstVisibleScrollingCol = dataGridColumnTmp;
            if (newFirstVisibleScrollingCol == null)
            {
                if (_negHorizontalOffset == 0)
                {
                    // no more column to display on the left of the first seen column
                    return;
                }

                newFirstVisibleScrollingCol = ColumnsItemsInternal[DisplayData.FirstDisplayedScrollingCol];
            }
        }

        double newColOffset = 0;
        foreach (DataGridColumn dataGridColumn in ColumnsInternal.GetVisibleScrollingColumns())
        {
            if (dataGridColumn == newFirstVisibleScrollingCol)
            {
                break;
            }

            newColOffset += GetEdgedColumnWidth(dataGridColumn);
        }

        UpdateHorizontalOffset(newColOffset);
    }

    private void UpdateDisplayedColumns()
    {
        DisplayData.FirstDisplayedScrollingCol = ComputeFirstVisibleScrollingColumn();
        ComputeDisplayedColumns();
    }

    private static DataGridBoundColumn GetDataGridColumnFromType(Type type, DataGrid ownerGrid)
    {
        Debug.Assert(type != null);
        if (type == typeof(bool))
        {
            return new DataGridCheckBoxColumn();
        }

        if (type == typeof(bool?))
        {
            return new DataGridCheckBoxColumn()
            {
                IsThreeState = true
            };
        }

        return new DataGridTextColumn();
    }

    private void AutoGenerateColumnsPrivate()
    {
        if (!_measured || (_autoGeneratingColumnOperationCount > 0))
        {
            // Reading the DataType when we generate columns could cause the CollectionView to 
            // raise a Reset if its Enumeration changed.  In that case, we don't want to generate again.
            return;
        }

        _autoGeneratingColumnOperationCount++;
        try
        {
            // Always remove existing autogenerated columns before generating new ones
            RemoveAutoGeneratedColumns();
            GenerateColumnsFromProperties();
            EnsureRowsPresenterVisibility();
            InvalidateRowHeightEstimate();
        }
        finally
        {
            _autoGeneratingColumnOperationCount--;
        }
    }

    private void GenerateColumnsFromProperties()
    {
        // Autogenerated Columns are added at the end so the user columns appear first
        if (DataConnection.DataProperties.Length > 0)
        {
            List<KeyValuePair<int, DataGridAutoGeneratingColumnEventArgs>> columnOrderPairs =
                new List<KeyValuePair<int, DataGridAutoGeneratingColumnEventArgs>>();

            // Generate the columns
            foreach (PropertyInfo propertyInfo in DataConnection.DataProperties)
            {
                string columnHeader = propertyInfo.Name;
                int    columnOrder  = DefaultColumnDisplayOrder;

                // Check if DisplayAttribute is defined on the property
                object[] attributes = propertyInfo.GetCustomAttributes(typeof(DisplayAttribute), true);
                if (attributes.Length > 0)
                {
                    DisplayAttribute? displayAttribute = attributes[0] as DisplayAttribute;
                    Debug.Assert(displayAttribute != null);

                    bool? autoGenerateField = displayAttribute.GetAutoGenerateField();
                    if (autoGenerateField.HasValue && autoGenerateField.Value == false)
                    {
                        // Abort column generation because we aren't supposed to auto-generate this field
                        continue;
                    }

                    string? header = displayAttribute.GetShortName();
                    if (header != null)
                    {
                        columnHeader = header;
                    }

                    int? order = displayAttribute.GetOrder();
                    if (order.HasValue)
                    {
                        columnOrder = order.Value;
                    }
                }

                // Generate a single column and determine its relative order
                int insertIndex = 0;
                if (columnOrder == int.MaxValue)
                {
                    insertIndex = columnOrderPairs.Count;
                }
                else
                {
                    foreach (KeyValuePair<int, DataGridAutoGeneratingColumnEventArgs> columnOrderPair in
                             columnOrderPairs)
                    {
                        if (columnOrderPair.Key > columnOrder)
                        {
                            break;
                        }

                        insertIndex++;
                    }
                }

                DataGridAutoGeneratingColumnEventArgs columnArgs =
                    GenerateColumn(propertyInfo.PropertyType, propertyInfo.Name, columnHeader, this);
                columnOrderPairs.Insert(insertIndex,
                    new KeyValuePair<int, DataGridAutoGeneratingColumnEventArgs>(columnOrder, columnArgs));
            }

            // Add the columns to the DataGrid in the correct order
            foreach (KeyValuePair<int, DataGridAutoGeneratingColumnEventArgs> columnOrderPair in columnOrderPairs)
            {
                AddGeneratedColumn(columnOrderPair.Value);
            }
        }
        else if (DataConnection.DataIsPrimitive)
        {
            Debug.Assert(DataConnection.DataType != null);
            AddGeneratedColumn(
                GenerateColumn(DataConnection.DataType, string.Empty, DataConnection.DataType.Name, this));
        }
    }

    private static DataGridAutoGeneratingColumnEventArgs GenerateColumn(Type propertyType, string propertyName,
                                                                        string header,
                                                                        DataGrid ownerGrid)
    {
        // Create a new DataBoundColumn for the Property
        DataGridBoundColumn newColumn = GetDataGridColumnFromType(propertyType, ownerGrid);
        newColumn.Binding         = new Binding(propertyName);
        newColumn.Header          = header;
        newColumn.IsAutoGenerated = true;
        return new DataGridAutoGeneratingColumnEventArgs(propertyName, propertyType, newColumn);
    }

    private bool AddGeneratedColumn(DataGridAutoGeneratingColumnEventArgs e)
    {
        // Raise the AutoGeneratingColumn event in case the user wants to Cancel or Replace the
        // column being generated
        OnAutoGeneratingColumn(e);
        if (e.Cancel)
        {
            return false;
        }

        if (e.Column != null)
        {
            // Set the IsAutoGenerated flag here in case the user provides a custom autogenerated column
            e.Column.IsAutoGenerated = true;
            // TODO 需要审查
            ColumnsInternal.Add(e.Column);
            ColumnsInternal.AutogeneratedColumnCount++;
        }

        return true;
    }

    private void ProcessFrozenColumnCount()
    {
        CorrectColumnFrozenStates();
        ComputeScrollBarsLayout();

        InvalidateColumnHeadersArrange();
        InvalidateCellsArrange();
    }

    private void HandleGroupColumnsInternalCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.Action == NotifyCollectionChangedAction.Reset)
        {
            ColumnsInternal.Clear();
            return;
        }

        if (e.Action == NotifyCollectionChangedAction.Remove)
        {
            if (e.OldItems != null)
            {
                var removedColumns = new List<DataGridColumn>();
                foreach (var item in e.OldItems)
                {
                    if (item is DataGridColumn column)
                    {
                        removedColumns.Add(column);
                    }
                    else if (item is DataGridColumnGroupItem gridColumn)
                    {
                        gridColumn.OwningGrid = null;
                        var columns = CollectColumnsFromGroupTree(gridColumn);
                        removedColumns.AddRange(columns);
                    }
                }

                foreach (var column in removedColumns)
                {
                    ColumnsInternal.Remove(column);
                }
            }
        }

        if (e.Action == NotifyCollectionChangedAction.Add)
        {
            if (e.NewItems != null)
            {
                var addedColumns = new List<DataGridColumn>();
                foreach (var item in e.NewItems)
                {
                    if (item is DataGridColumn column)
                    {
                        addedColumns.Add(column);
                    }
                    else if (item is DataGridColumnGroupItem gridColumn)
                    {
                        gridColumn.OwningGrid = this;
                        var columns = CollectColumnsFromGroupTree(gridColumn);
                        addedColumns.AddRange(columns);
                    }
                }

                foreach (var column in addedColumns)
                {
                    ColumnsInternal.Add(column);
                }
            }
        }

        if (e.Action == NotifyCollectionChangedAction.Remove)
        {
            if (e.OldItems != null)
            {
                foreach (var item in e.OldItems)
                {
                    if (item is IDataGridColumnGroupItem gridItem)
                    {
                        gridItem.GroupChanged -= HandleColumnGroupItemChanged;
                    }
                }
            }
        }

        if (e.Action == NotifyCollectionChangedAction.Add)
        {
            if (e.NewItems != null)
            {
                foreach (var item in e.NewItems)
                {
                    if (item is IDataGridColumnGroupItem gridItem)
                    {
                        gridItem.GroupChanged += HandleColumnGroupItemChanged;
                    }
                }
            }
        }

        IsGroupHeaderMode = ColumnGroupsInternal.Count > 0;
    }

    private void HandleColumnGroupItemChanged(object? sender, DataGridColumnGroupChangedArgs args)
    {
        if (args.GroupItem is DataGridColumn gridColumn)
        {
            if (args.ChangedType == NotifyColumnGroupChangedType.Add)
            {
                ColumnsInternal.Add(gridColumn);
            }
            else if (args.ChangedType == NotifyColumnGroupChangedType.Remove)
            {
                ColumnsInternal.Remove(gridColumn);
            }
        }
    }

    private List<DataGridColumn> CollectColumnsFromGroupTree(IDataGridColumnGroupItem groupItem)
    {
        var columns = new List<DataGridColumn>();
        if (groupItem is DataGridColumn column)
        {
            columns.Add(column);
        }
        else if (groupItem is DataGridColumnGroupItem gridColumnGroup)
        {
            gridColumnGroup.OwningGrid = this;
        }

        foreach (var child in groupItem.GroupChildren)
        {
            var childColumns = CollectColumnsFromGroupTree(child);
            columns.AddRange(childColumns);
        }

        return columns;
    }
}